package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestStringListIntegration(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		// Test string->list
		{`(string->list "abc")`, "(#\\a #\\b #\\c)"},
		{`(string->list "")`, "()"},
		
		// Test list->string - fixing the test case to use proper character syntax
		{`(list->string (list #\a #\b #\c))`, "\"abc\""},
		{`(list->string (list))`, "\"\""},
		
		// Test string-copy
		{`(string-copy "hello")`, "\"hello\""},
		
		// Combined tests
		{`(list->string (string->list "test"))`, "\"test\""},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		
		if err != nil {
			t.Fatalf("Failed to parse program: %s, error: %v", tt.input, err)
		}

		var result obj.Object
		for _, expr := range exprs {
			result, err = EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression error = %v", err)
			}
		}

		if result == nil {
			t.Errorf("Eval returned nil for input: %s", tt.input)
			continue
		}

		if result.Type() == obj.ERROR_OBJ {
			t.Errorf("Error evaluating %s: %s", tt.input, result.Inspect())
			continue
		}

		if result.Inspect() != tt.expected {
			t.Errorf("Input: %s\nExpected: %s\nGot: %s", tt.input, tt.expected, result.Inspect())
		}
	}
}

func TestStringFillIntegration(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	// Test string-fill!
	input := `
(let ((s (string-copy "hello")))
  (string-fill! s #\x)
  s)
`
	lexer := lex.NewLexer(input)
	tokens := lexer.ReadTokens()
	parser := parse.NewParser(tokens)
	exprs, err := parser.ParseProgram()
	
	if err != nil {
		t.Fatalf("Failed to parse program, error: %v", err)
	}

	var result obj.Object
	for _, expr := range exprs {
		result, err = EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression error = %v", err)
		}
	}

	if result == nil {
		t.Fatalf("Eval returned nil")
	}

	if result.Type() == obj.ERROR_OBJ {
		t.Errorf("Error evaluating: %s", result.Inspect())
	}

	if result.Inspect() != "\"xxxxx\"" {
		t.Errorf("Expected \"xxxxx\", got %s", result.Inspect())
	}
}

func TestStringProceduresErrorCases(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		// Test string->list error cases
		{
			name:     "string->list with no arguments",
			input:    `(string->list)`,
			expected: "string->list requires exactly 1 argument",
		},
		{
			name:     "string->list with too many arguments",
			input:    `(string->list "abc" "def")`,
			expected: "string->list requires exactly 1 argument",
		},
		{
			name:     "string->list with wrong argument type",
			input:    `(string->list 123)`,
			expected: "string->list argument must be a string",
		},
		
		// Test list->string error cases
		{
			name:     "list->string with no arguments",
			input:    `(list->string)`,
			expected: "list->string requires exactly 1 argument",
		},
		{
			name:     "list->string with too many arguments",
			input:    `(list->string (list #\a #\b) (list #\c #\d))`,
			expected: "list->string requires exactly 1 argument",
		},
		{
			name:     "list->string with wrong argument type",
			input:    `(list->string 123)`,
			expected: "list->string argument must be a proper list",
		},
		{
			name:     "list->string with non-character elements",
			input:    `(list->string (cons #\a 123))`,
			expected: "all elements of list must be characters",
		},
		
		// Test string-copy error cases
		{
			name:     "string-copy with no arguments",
			input:    `(string-copy)`,
			expected: "string-copy requires exactly 1 argument",
		},
		{
			name:     "string-copy with too many arguments",
			input:    `(string-copy "abc" "def")`,
			expected: "string-copy requires exactly 1 argument",
		},
		{
			name:     "string-copy with wrong argument type",
			input:    `(string-copy 123)`,
			expected: "string-copy argument must be a string",
		},
		
		// Test string-fill! error cases
		{
			name:     "string-fill! with no arguments",
			input:    `(string-fill!)`,
			expected: "string-fill! requires exactly 2 arguments",
		},
		{
			name:     "string-fill! with too few arguments",
			input:    `(string-fill! "abc")`,
			expected: "string-fill! requires exactly 2 arguments",
		},
		{
			name:     "string-fill! with too many arguments",
			input:    `(string-fill! "abc" #\x "extra")`,
			expected: "string-fill! requires exactly 2 arguments",
		},
		{
			name:     "string-fill! with wrong first argument type",
			input:    `(string-fill! 123 #\x)`,
			expected: "string-fill! first argument must be a string",
		},
		{
			name:     "string-fill! with wrong second argument type",
			input:    `(string-fill! "abc" 123)`,
			expected: "string-fill! second argument must be a character",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			exprs, err := parser.ParseProgram()
			
			if err != nil {
				t.Fatalf("Failed to parse program: %s, error: %v", tt.input, err)
			}

			var result obj.Object
			for _, expr := range exprs {
				result, err = EvalExpression(expr, env)
				// For error test cases, we expect an error to be returned
				if err != nil {
					// Error occurred during evaluation, check if it matches expected error
					if err.Error() != tt.expected {
						t.Errorf("EvalExpression() error = %v, want %v", err.Error(), tt.expected)
					}
					return
				}
				
				if result == nil {
					t.Fatalf("Eval returned nil for input: %s", tt.input)
				}

				if result.Type() != obj.ERROR_OBJ {
					t.Fatalf("Expected error for input: %s, got: %s", tt.input, result.Inspect())
				}

				errorObj := result.(*Error)
				if errorObj.Message != tt.expected {
					t.Errorf("Input: %s\nExpected error: %s\nGot error: %s", tt.input, tt.expected, errorObj.Message)
				}
				
				// Break after the first expression since we're testing error cases
				break
			}
		})
	}
}